nativetypes.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import typing as t
  2. from ast import literal_eval
  3. from ast import parse
  4. from itertools import chain
  5. from itertools import islice
  6. from types import GeneratorType
  7. from . import nodes
  8. from .compiler import CodeGenerator
  9. from .compiler import Frame
  10. from .compiler import has_safe_repr
  11. from .environment import Environment
  12. from .environment import Template
  13. def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
  14. """Return a native Python type from the list of compiled nodes. If
  15. the result is a single node, its value is returned. Otherwise, the
  16. nodes are concatenated as strings. If the result can be parsed with
  17. :func:`ast.literal_eval`, the parsed value is returned. Otherwise,
  18. the string is returned.
  19. :param values: Iterable of outputs to concatenate.
  20. """
  21. head = list(islice(values, 2))
  22. if not head:
  23. return None
  24. if len(head) == 1:
  25. raw = head[0]
  26. if not isinstance(raw, str):
  27. return raw
  28. else:
  29. if isinstance(values, GeneratorType):
  30. values = chain(head, values)
  31. raw = "".join([str(v) for v in values])
  32. try:
  33. return literal_eval(
  34. # In Python 3.10+ ast.literal_eval removes leading spaces/tabs
  35. # from the given string. For backwards compatibility we need to
  36. # parse the string ourselves without removing leading spaces/tabs.
  37. parse(raw, mode="eval")
  38. )
  39. except (ValueError, SyntaxError, MemoryError):
  40. return raw
  41. class NativeCodeGenerator(CodeGenerator):
  42. """A code generator which renders Python types by not adding
  43. ``str()`` around output nodes.
  44. """
  45. @staticmethod
  46. def _default_finalize(value: t.Any) -> t.Any:
  47. return value
  48. def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
  49. return repr("".join([str(v) for v in group]))
  50. def _output_child_to_const(
  51. self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
  52. ) -> t.Any:
  53. const = node.as_const(frame.eval_ctx)
  54. if not has_safe_repr(const):
  55. raise nodes.Impossible()
  56. if isinstance(node, nodes.TemplateData):
  57. return const
  58. return finalize.const(const) # type: ignore
  59. def _output_child_pre(
  60. self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
  61. ) -> None:
  62. if finalize.src is not None:
  63. self.write(finalize.src)
  64. def _output_child_post(
  65. self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
  66. ) -> None:
  67. if finalize.src is not None:
  68. self.write(")")
  69. class NativeEnvironment(Environment):
  70. """An environment that renders templates to native Python types."""
  71. code_generator_class = NativeCodeGenerator
  72. concat = staticmethod(native_concat) # type: ignore
  73. class NativeTemplate(Template):
  74. environment_class = NativeEnvironment
  75. def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
  76. """Render the template to produce a native Python type. If the
  77. result is a single node, its value is returned. Otherwise, the
  78. nodes are concatenated as strings. If the result can be parsed
  79. with :func:`ast.literal_eval`, the parsed value is returned.
  80. Otherwise, the string is returned.
  81. """
  82. ctx = self.new_context(dict(*args, **kwargs))
  83. try:
  84. return self.environment_class.concat( # type: ignore
  85. self.root_render_func(ctx) # type: ignore
  86. )
  87. except Exception:
  88. return self.environment.handle_exception()
  89. async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
  90. if not self.environment.is_async:
  91. raise RuntimeError(
  92. "The environment was not created with async mode enabled."
  93. )
  94. ctx = self.new_context(dict(*args, **kwargs))
  95. try:
  96. return self.environment_class.concat( # type: ignore
  97. [n async for n in self.root_render_func(ctx)] # type: ignore
  98. )
  99. except Exception:
  100. return self.environment.handle_exception()
  101. NativeEnvironment.template_class = NativeTemplate